﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using TCCHWLib.Commands;
using System.Drawing;
using TCCUIControls;

namespace TCCHWLib
{
    public class Processor : IDisposable, IBusComponent, IComparable<Processor>
    {
        private CacheMemory m_Cache = null;
        private SignatureRegister m_ReadSignatureRegister = null;
        private SignatureRegister m_WriteSignatureRegister = null;
        private Queue<BatchCommand> m_Commands = null;
        private UIProcessor m_UiProcessor = null;
        private PriorityKey m_Priority = null;
        private Thread m_RunningThread = null;
        private EventWaitHandle m_WaitHandler = null;
        private object m_Locker = new object();
        private string m_Name = "";
        private bool m_IsCommitReady = false;
        private bool m_Commiting = false;
        private int m_PC = -1;
        private IDictionary<string, int> m_LiteralTable = null;
        private WriteSet m_WriteSet = null;
        private BatchCommand m_CurrentBatch = null;
        private static IDictionary<string, LiteralTable> m_GlobalLiteralTable = new Dictionary<string, LiteralTable>();

        public static IDictionary<string, LiteralTable> GlobalLiteralTable
        {
            get { return Processor.m_GlobalLiteralTable; }
            set { Processor.m_GlobalLiteralTable = value; }
        }

        public WriteSet WriteSet
        {
            get { return m_WriteSet; }
            set { m_WriteSet = value; }
        }

        public IDictionary<string, int> LiteralTable
        {
            get { return m_LiteralTable; }
            set { m_LiteralTable = value; }
        }

        public int PC
        {
            get { return m_PC; }
            set { m_PC = value; }
        }

        public bool IsCommitReady
        {
            get { return m_IsCommitReady; }
            set { m_IsCommitReady = value; }
        }

        public PriorityKey Priority
        {
            get { return m_Priority; }
            set
            {
                m_Priority = value;
                Chip.Instance.Arbiter.UpdateListView();
            }
        }

        public UIProcessor UiProcessor
        {
            get { return m_UiProcessor; }
            set { m_UiProcessor = value; }
        }

        public Queue<BatchCommand> Commands
        {
            get { return m_Commands; }
        }

        public string Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        public SignatureRegister ReadSignatureRegister
        {
            get { return m_ReadSignatureRegister; }
            set { m_ReadSignatureRegister = value; }
        }
        public SignatureRegister WriteSignatureRegister
        {
            get { return m_WriteSignatureRegister; }
            set { m_WriteSignatureRegister = value; }
        }
        public CacheMemory Cache
        {
            get { return m_Cache; }
            set { m_Cache = value; }
        }

        public Processor(string name, Size cacheSize)
        {
            m_Name = name;
            m_Cache = new CacheMemory(cacheSize, this);

            m_UiProcessor = new UIProcessor(name, Cache.ListView);

            InitializeProcessor();
        }

        private void InitializeProcessor()
        {
            m_Commands = new Queue<BatchCommand>();
            m_WaitHandler = new AutoResetEvent(false);
            Priority = new PriorityKey(int.MaxValue, int.MaxValue);
            m_ReadSignatureRegister = new SignatureRegister(Configuration.Instance.SignatureRegisterSize);
            m_WriteSignatureRegister = new SignatureRegister(Configuration.Instance.SignatureRegisterSize);
            m_LiteralTable = new Dictionary<string, int>();
            m_WriteSet = new WriteSet(new Size(1, 128));
            Chip.Instance.Arbiter.AddProcessor(this);

            // TODO : Do this ..
            m_RunningThread = new Thread(new ThreadStart(DoWork));
            m_RunningThread.IsBackground = true;
            m_RunningThread.Start();
        }

        private void DoWork()
        {
            while (true)
            {
                lock (m_Locker)
                {
                    if (m_Commands.Count > 0)
                    {
                        m_CurrentBatch = m_Commands.Peek();

                        if (m_CurrentBatch == null)// Exit thread
                            return;

                    }
                    // TODO: Process task here ...
                    if (m_CurrentBatch != null)
                    {
                        m_PC = 0;
                        m_Priority = m_CurrentBatch.Priority;
                        m_UiProcessor.SetPriority(m_Priority.ToString());

                        foreach (ICommand command in m_CurrentBatch.CommandList)
                            command.ParentBatch = m_CurrentBatch;

                        while (!(bool)m_CurrentBatch.Execute(this))
                        {
                            Rollback(true);
                            Cache.UpdateListView();
                        }

                        //m_Commands.Dequeue();
                    }
                    else
                        m_WaitHandler.WaitOne();
                }
            }
        }

        public void EnqueueTask(ICommand command)
        {
            if (command is BatchCommand)
                EnqueueTask(command as BatchCommand);
            else
                throw new Exception("Enqueuing a command that is not a batch command!");
        }

        public void EnqueueTask(BatchCommand command)
        {
            //lock (m_Locker)
            {
                m_Commands.Enqueue(command);

                if (command == null)
                    return;

                UpdateListView();
                
                //using (BatchCommand cmd = command as BatchCommand)
                //{
                //    foreach (ICommand c in cmd.CommandList)
                //        m_UiProcessor.EnqueueTask(c.ToString());
                //}
            }

            // Wake thread
            m_WaitHandler.Set();
        }

        delegate void UpdateListViewDelegate();
        public void UpdateListView()
        {
            if (m_UiProcessor.ListView.InvokeRequired)
            {
                m_UiProcessor.ListView.BeginInvoke(new UpdateListViewDelegate(UpdateListView));
                return;
            }

            lock (m_UiProcessor.ListView)
            {
                m_UiProcessor.ListView.Items.Clear();

                if (m_CurrentBatch != null)
                    foreach (ICommand cmd in m_CurrentBatch.CommandList)
                    {
                        m_UiProcessor.ListView.Items.Add(cmd.ToString());
                    }
            }
        }

        public void Signal()
        {
            m_WaitHandler.Set();
            Commit();
           // Reset();
        }

        public bool didRollback = false;
        public void Rollback(bool printMsg)
        {
            if (!printMsg)
            {
                if (IsCommitReady)
                    Chip.WriteToConsole(this.Name + " will rollback!\n(Violated while Waiting)");
                else
                    Chip.WriteToConsole(this.Name + " will rollback!\n(Violated while Processing)");
            }

            m_CurrentBatch.Undo(this);
            m_CurrentBatch = m_Commands.Peek();
            Reset();
            PC = 0;
            m_Priority = m_CurrentBatch.Priority;
            m_IsCommitReady = false;
            didRollback = false;
            m_WaitHandler.Set();
        }

        public void Reset()
        {
            /// 1.  Reset PC to -1 (to halt batch) and other vars
            m_PC = -1;
            m_IsCommitReady = false;
            m_Priority = m_CurrentBatch.Priority;

            /// 2.  Clear Read & Write Signatures
            m_ReadSignatureRegister.ResetRegister();
            m_WriteSignatureRegister.ResetRegister();
            //m_LiteralTable.Clear();
            /// 3.  Clear Write Buffer
            m_WriteSet.Reset();
            /// 4.  Restart Execution (should be done automatically) by
            ///     adjusting the PC ...

        }
        public void Commit()
        {
            //lock (this)
            //{
                /// 1.  Commit Write Buffer
                //WriteCommand Write;
                m_Commiting = true;
                foreach (int address in m_WriteSet.Buffer.Keys)
                {
                    Chip.Instance.Bus.CheckReadSignature(address);
                    Chip.Instance.SharedMemory.Write(address,m_WriteSet[address]);
                    if (Cache.Buffer.ContainsKey(address))
                        Cache.Buffer[address].Value = m_WriteSet[address];
                }
                Chip.Instance.SharedMemory.UpdateListView();
                Cache.UpdateListView();
                /// 2.  Resetting the Processor will be on the next TXBeginCommand ...
                m_Commiting = false;
                m_IsCommitReady = false;
                m_ReadSignatureRegister.ResetRegister();
                m_WriteSignatureRegister.ResetRegister();
                m_WriteSet.Reset();
                m_Cache.ClearWriteSet();
                m_Cache.ClearRest();
                Chip.WriteToConsole(Name + " committed " + m_Priority);
                Priority = new PriorityKey(int.MaxValue, int.MaxValue);
            //}
                m_Commands.Dequeue();
        }

        public void Decode(ICommand cmd)
        {
            ParameterType type1 = cmd.Type1;
            switch (type1)
            {
                case ParameterType.Variable:
                    int address = ReturnAddress(Convert.ToString(cmd.Operand1));
                    cmd.ExOperand1 = address;
                    cmd.ExType1 = ParameterType.Address;
                    break;
                case ParameterType.Instruction:
                    int index = Convert.ToInt32(cmd.Operand1);
                    cmd.ExOperand1 = cmd.ParentBatch[index].Value;
                    cmd.ExType1 = ParameterType.Value;
                    break;

            }
            ParameterType type2 = cmd.Type2;
            switch (type2)
            {

                case ParameterType.Variable:
                    int address = ReturnAddress(Convert.ToString(cmd.Operand2));
                    cmd.ExOperand2 = address;
                    cmd.ExType2 = ParameterType.Address;
                    break;
                case ParameterType.Instruction:
                    int index = Convert.ToInt32(cmd.Operand2);
                    cmd.ExOperand2 = cmd.ParentBatch[index].Value;
                    cmd.ExType2 = ParameterType.Value;
                    break;


            }
            ParameterType Type3 = cmd.Type3;
            switch (Type3)
            {
                case ParameterType.Variable:
                    int address = ReturnAddress(Convert.ToString(cmd.Operand3));
                    cmd.ExOperand3 = address;
                    cmd.ExType3 = ParameterType.Address;
                    break;
                case ParameterType.Instruction:
                    int index = Convert.ToInt32(cmd.Operand3);
                    cmd.ExOperand3 = cmd.ParentBatch[index].Value;
                    cmd.ExType3 = ParameterType.Value;
                    break;

            }

        }

        int ReturnAddress(string variable)
        {
            lock (m_GlobalLiteralTable)
            {
                if (!m_GlobalLiteralTable.ContainsKey(variable) && !m_LiteralTable.ContainsKey(variable))
                    throw new Exception("variable not found in Literal Table");

                if (m_GlobalLiteralTable.ContainsKey(variable))
                    return m_GlobalLiteralTable[variable].Address;
                else
                    return m_LiteralTable[variable];
            }

        }

        #region IDisposable Members

        public void Dispose()
        {
            EnqueueTask(null);          // Signal the consumer to exit
            m_RunningThread.Join();     // Wait for the thread to finish
            m_WaitHandler.Close();      // Release any OS resources
        }

        #endregion

        #region IComparable<Processor> Members

        public int CompareTo(Processor other)
        {
            return this.Priority.CompareTo(other.Priority);
        }

        #endregion

        public override string ToString()
        {
            if (m_Priority != null && m_Priority.Phase != int.MaxValue)
                return String.Format("{0} [{1}, {2}]", m_Name, m_Priority.Phase, m_Priority.Sequence);
            else
                return String.Format("{0}", m_Name);
        }

        #region IBusComponent Members

        public void NotifyRead(int address)
        {
            if (Cache.ContainValue(address))
                Cache.MesiShared(address);
        }

        public void NotifyWrite(int address)
        {
            Cache.MesiInvalid(address);
            didRollback = true;
            Rollback(false);
        }

        public bool IsRead(int address)
        {
            bool read = Cache.ContainValue(address);
            if (read)
                Cache.MesiShared(address);
            return read;
        }

        public bool CheckWriteSignature(int address)
        {
            return m_WriteSignatureRegister.LookUp(address);
        }

        public bool CheckReadSignature(int address)
        {
            if (m_ReadSignatureRegister.LookUp(address) && !m_Commiting)
            {
                NotifyWrite(address);
                return true;
            }
            return false;

        }

        #endregion

        internal void Suspend()
        {
            m_WaitHandler.WaitOne();
        }

        internal void Proceed()
        {
            if (m_CurrentBatch == null)
                return;

            m_CurrentBatch.Proceed();
        }
    }
}